#pragma once

#include <iostream>
#include <vector>
#include <queue>
#include <mutex>
#include <pthread.h>
#include <cassert>
#include "Thread.h"
#include "Mutex.h"
#include "Task.h"

// 声明
template<class T>
class ThreadPool;

// @brief 线程池中线程的上下文
template<class T>
struct ThreadData {
    ThreadPool<T>* _ptr_thread_pool; // 指向线程池的指针
    std::string _thread_name;

    ThreadData(ThreadPool<T>* ptr_thread_pool, const std::string& thread_name)
        : _ptr_thread_pool(ptr_thread_pool)
        , _thread_name(thread_name)
    {}
};

template<class T>
class ThreadPool {
private:
    typedef ThreadPool<T> self;
    typedef struct ThreadData<T> ThreadData;

private:
    int _thread_num; // 线程池中的线程数
    std::vector<Thread*> _threads; // 组织线程的数组
    std::queue<T> _task_queue; // 任务队列，线程从中获取任务
    pthread_mutex_t _mutex; // 互斥锁，保证访问任务队列时是互斥的
    pthread_cond_t _cond; // 条件变量，保证只在任务队列中有任务才拿取任务
    static std::mutex _singleton_mutex; // 互斥锁，保证单例模式是线程安全的
    static self* _ptr_thread_pool; // 线程池的指针

private:
    //* 为了实现单例模式，需要将构造函数私有
    ThreadPool(const int& thread_num)
        : _thread_num(thread_num)
    {
        // char name_buffer[128] = {};
        // _threads.reserve(thread_num);

        // for (int i = 0; i < thread_num; ++i) {
        //     std::snprintf(name_buffer, sizeof(name_buffer), "thread[%d]", i);
        //     ThreadData* thread_data = new ThreadData(this/*getSingleton()*/, name_buffer);
        //     _threads.push_back(new Thread(handleTask, (void*)thread_data));
        // }

        // _threads.resize(thread_num, nullptr);

        _threads.reserve(thread_num);
        for (int i = 0; i < thread_num; ++i) {
            _threads.push_back(new Thread());
        }

        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }

    // 线程的执行函数写成static是因为start_routine函数要求参数只有一个，类型为void*
    // 但是如果不加static的话，由于handleTask是成员函数，自带this指针
    // 所以要用static来消除this指针，这也是为什么给handleTask函数传入的上下文中要有内存池指针的原因
    // 因为如果没有内存池指针，就无法访问类内的非static成员或方法
    static void* handleTask(void* args) {
        ThreadData* ptr_thread_data = static_cast<ThreadData*>(args);

        std::cout << ptr_thread_data->_thread_name << " 启动！" << std::endl;

        while (true) {
            T task;

            // _task_queue是共享资源，要将其保护起来，保证同时只能有一个线程从中获取任务
            {
                LockGuard lock_guard(ptr_thread_data->_ptr_thread_pool->getMutex());
                while (ptr_thread_data->_ptr_thread_pool->isQueueEmpty()) {
                    // 如果任务队列中没有任务，就在条件变量这里挂起等待
                    ptr_thread_data->_ptr_thread_pool->threadWait();
                }

                // 获取任务应在临界区内，因为是在对_task_queue这个共享资源做访问
                task = ptr_thread_data->_ptr_thread_pool->pop();
            }

            // 来到这里说明任务队列中有任务，那就获取任务并处理
            //* 处理任务的过程应在临界区外，这样保证了获取任务是串行的但是处理任务是并行的
            // 体现了生产消费模型的高效性
            std::cout << ptr_thread_data->_thread_name << " 获取了一个任务: " << task.toString()
                << " 任务处理完成: " << task() << std::endl;
        }

        delete ptr_thread_data;
        return nullptr;
    }

private: // 封装了一些小组件
    void lockQueue() {
        pthread_mutex_lock(&_mutex);
    }

    void unlockQueue() {
        pthread_mutex_unlock(&_mutex);
    }

    bool isQueueEmpty() {
        return _task_queue.empty();
    }

    void threadWait() {
        pthread_cond_wait(&_cond, &_mutex);
    }

    void threadNotify() {
        pthread_cond_signal(&_cond);
    }

    // 该pop函数本身是在临界区内被使用的，所以不需要额外保护
    T pop() {
        // assert(!isQueueEmpty());
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }

    pthread_mutex_t* getMutex() {
        return &_mutex;
    }

public:
    ThreadPool(const self& thread_pool) = delete;
    self& operator=(const self& thread_pool) = delete;
    ThreadPool(self&& thread_pool) = default;
    self& operator=(self&& thread_pool) = default;

    ~ThreadPool() {
        for (auto& ptr_thread : _threads) {
            delete ptr_thread;
            ptr_thread = nullptr;
        }

        delete _ptr_thread_pool;
        _ptr_thread_pool = nullptr;

        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

    // static使该函数可以通过类名来调用，可以解决没有公开的构造函数的问题
    // 但是也要求其中使用的成员变量都是static的，如_singleton_mutex和_ptr_thread_pool
    static self* getSingleton(const int& thread_num = 5) {

        // 避免在已经创建了单例后，每次通过该函数获取ThreadPool的指针时都要申请互斥锁，从而导致效率降低的情况
        if (nullptr == _ptr_thread_pool) {

            // 保证线程安全，避免同时多个线程访问_ptr_thread_pool，避免了在已经构建出单例后再次构建的可能性
            std::lock_guard<std::mutex> singleton_lock_guard(_singleton_mutex);
            if (nullptr == _ptr_thread_pool) {
                _ptr_thread_pool = new ThreadPool<T>(thread_num);
            }
        }

        return _ptr_thread_pool;
    }

    void push(const T& in) {
        LockGuard lock_guard(&_mutex); //! 这里不要写成匿名对象了！如LockGuard(&_mutex)
        _task_queue.push(in); // 因为我们没有限制任务队列的最大容量，所以随便放任务，不需要条件变量
        threadNotify(); // 通知线程池中的线程来领取任务
    }

    void boost() {
        char name_buffer[128] = {};
        int i = 1;

        for (auto& ptr_thread : _threads) {

            std::snprintf(name_buffer, sizeof(name_buffer), "thread[%d]", i++);
            ThreadData* thread_data = new ThreadData(this/*getSingleton()*/, name_buffer);
            // ptr_thread = new Thread(handleTask, (void*)thread_data);
            // ptr_thread->start();

            //! 不知道为什么，不能够在线程池的构造函数中，在创建线程时同时将处理方法和参数传递给线程
            //! 如果那样做的话，会报传递给线程的处理方法为空
            //! 这是一个很神奇的现象，我现在还不懂，等以后看有没有什么办法解决
            //! 我目前认为这是单例模式懒加载带来bug，但是还没有测(确定)，等之后有空了再搞定
            ptr_thread->start(handleTask, (void*)thread_data);
        }
    }
};

template<class T>
std::mutex ThreadPool<T>::_singleton_mutex; // 定义单例模式互斥锁，但没有初始化

template<class T>
ThreadPool<T>* ThreadPool<T>::_ptr_thread_pool = nullptr; // 定义线程池指针，并初始化为nullptr